#include<string.h>
#include <stdlib.h>
#include "global.h"

/* table to tell if char can be part of a C identifier. */
extern char is_idchar[256];
/* table to tell if char can be first char of a c identifier. */
extern char is_idstart[256];
/* table to tell if c is horizontal space.  isspace() thinks that
   newline is space; this is not a good idea for this program. */
extern char is_hor_space[256];
extern char is_space[256];

#define SKIP_WHITE_SPACE(p) do { while (is_hor_space[*p]) p++; } while (0)
#define SKIP_ALL_WHITE_SPACE(p) do { while (is_space[*p]) p++; }  while (0)
int traditional=1;

void bcopy (register unsigned char *b1, register unsigned char *b2, register int length)
{
	while (length-- > 0)
		*b2++ = *b1++;
}

/* Read a replacement list for a macro with parameters.
   Build the DEFINITION structure.
   Reads characters of text starting at BUF until LIMIT.
   ARGLIST specifies the formal parameters to look for
   in the text of the definition; NARGS is the number of args
   in that list, or -1 for a macro name that wants no argument list.
   MACRONAME is the macro name itself (so we can avoid recursive expansion)
   and NAMELEN is its length in characters.
   
Note that comments and backslash-newlines have already been deleted
from the argument.  */

/* Leading and trailing Space, Tab, etc. are converted to markers
   Newline Space, Newline Tab, etc.
   Newline Space makes a space in the final output
   but is discarded if stringified.  (Newline Tab is similar but
   makes a Tab instead.)

   If there is no trailing whitespace, a Newline Space is added at the end
   to prevent concatenation that would be contrary to the standard.  */

DEFINITION *collect_expansion (U_CHAR *buf,U_CHAR *end,int nargs,struct arglist * arglist)
{
  DEFINITION *defn;
  register U_CHAR *p, *limit, *lastp, *exp_p;
  struct reflist *endpat = NULL;
  /* Pointer to first nonspace after last ## seen.  */
  U_CHAR *concat = 0;
  /* Pointer to first nonspace after last single-# seen.  */
  U_CHAR *stringify = 0;

  /* Scan thru the replacement list, ignoring comments and quoted
     strings, picking up on the macro calls.  It does a linear search
     thru the arg list on every potential symbol.  Profiling might say
     that something smarter should happen. */

  if (end < buf)
    return NULL;

  defn = (DEFINITION *) xcalloc (1, sizeof (DEFINITION) + 2 * (end - buf + 2));

  defn->nargs = nargs;
  exp_p = defn->expansion = (U_CHAR *) defn + sizeof (DEFINITION);
  lastp = exp_p;

  /* Speed this loop up later? */

  p = buf;
  limit = end;

  /* Find the beginning of the trailing whitespace.  */
  while (p < limit && is_space[limit[-1]]) limit--;

  /* Convert leading whitespace to Newline-markers.  */
  while (p < limit && is_space[*p]) {
    *exp_p++ = '\n';
    *exp_p++ = *p++;
  }

  /* Process the main body of the definition.  */
  while (p < limit) {
    int skipped_arg = 0;
    register U_CHAR c = *p++;

    *exp_p++ = c;

    switch (c) {
    case '\'':
    case '\"':
      if (1) {
	for (; p < limit && *p != c; p++) {
	  *exp_p++ = *p;
	  if (*p == '\\') {
	    *exp_p++ = *++p;
	  }
	}
	*exp_p++ = *p++;
      }
      break;

      /* Special hack: if a \# is written in the #define
	 include a # in the definition.  This is useless for C code
	 but useful for preprocessing other things.  */

    case '\\':
      if (p < limit && *p == '#') {
	/* Pass through this # */
	exp_p--;
	*exp_p++ = *p++;
      }
      break;

    case '#':
      if (p < limit && *p == '#') {
	/* ##: concatenate preceding and following tokens.  */
	/* Take out the first #, discard preceding whitespace.  */
	exp_p--;
	while (exp_p > lastp && is_hor_space[exp_p[-1]])
	  --exp_p;
	/* Skip the second #.  */
	p++;
	/* Discard following whitespace.  */
	SKIP_WHITE_SPACE (p);
	concat = p;
      } else {
	/* Single #: stringify following argument ref.
	   Don't leave the # in the expansion.  */
	exp_p--;
	SKIP_WHITE_SPACE (p);
	if (p == limit || ! is_idstart[*p] || nargs <= 0)
	  error ("# operator should be followed by a macro argument name\n");
	else
	  stringify = p;
      }
      break;
    }

    if (is_idchar[c] && nargs > 0) {
      U_CHAR *id_beg = p - 1;
      int id_len;

      --exp_p;
      while (p != limit && is_idchar[*p]) p++;
      id_len = p - id_beg;

      if (is_idstart[c]) {
	register struct arglist *arg;

	for (arg = arglist; arg != NULL; arg = arg->next) {
	  struct reflist *tpat;

	  if (arg->name[0] == c
	      && arg->length == id_len
	      && strncmp ((char *)arg->name, (char*)id_beg, id_len) == 0) {
	    /* make a pat node for this arg and append it to the end of
	       the pat list */
	    tpat = (struct reflist *) malloc (sizeof (struct reflist));
	    tpat->next = NULL;
	    tpat->stringify = stringify == id_beg;
	    tpat->raw_before = concat == id_beg;
	    tpat->raw_after = 0;

	    if (endpat == NULL)
	      defn->pattern = tpat;
	    else
	      endpat->next = tpat;
	    endpat = tpat;

	    tpat->argno = arg->argno;
	    tpat->nchars = exp_p - lastp;
	    {
	      register U_CHAR *p1 = p;
	      SKIP_WHITE_SPACE (p1);
	      if (p1 + 2 <= limit && p1[0] == '#' && p1[1] == '#')
		tpat->raw_after = 1;
	    }
	    lastp = exp_p;	/* place to start copying from next time */
	    skipped_arg = 1;
	    break;
	  }
	}
      }

      /* If this was not a macro arg, copy it into the expansion.  */
      if (! skipped_arg) {
	register U_CHAR *lim1 = p;
	p = id_beg;
	while (p != lim1)
	  *exp_p++ = *p++;
	if (stringify == id_beg)
	  error ("# operator should be followed by a macro argument name\n");
      }
    }
  }

  if (limit < end) {
    /* Convert trailing whitespace to Newline-markers.  */
    while (limit < end && is_space[*limit]) {
      *exp_p++ = '\n';
      *exp_p++ = *limit++;
    }
  } else if (!traditional) {
    /* There is no trailing whitespace, so invent some.  */
    *exp_p++ = '\n';
    *exp_p++ = ' ';
  }

  *exp_p = '\0';

  defn->length = exp_p - defn->expansion;

#if 0
/* This isn't worth the time it takes.  */
  /* give back excess storage */
  defn->expansion = (U_CHAR *) xrealloc (defn->expansion, defn->length + 1);
#endif

  return defn;
}
